
// Copyright (C) 2010-2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;

using Derm.OpenGL;

namespace Derm.Render
{
	/// <summary>
	/// Culling face state
	/// </summary>
	public class CullFaceState : RenderState
	{
		#region Constructors
		
		/// <summary>
		/// Construct a default CullFaceState (front face is CCW, culling backfaces).
		/// </summary>
		public CullFaceState()
		{
			
		}

		/// <summary>
		/// Construct a CullFaceState, specifying front face and disabling culling for all faces.
		/// </summary>
		/// <param name="frontFace">
		/// A <see cref="FrontFace"/> that determine how front faces are determined.
		/// </param>
		public CullFaceState(FrontFace frontFace)
		{
			mFrontFaceMode = frontFace;
			mEnabled = false;
		}

		/// <summary>
		/// Construct a CullFaceState, specifying front face and enabling culling of the specified faces.
		/// </summary>
		/// <param name="frontFace">
		/// A <see cref="FrontFace"/> that specify how front faces are determined.
		/// </param>
		/// <param name="culledFace">
		/// A <see cref="Face"/> that specify which faces are culled.
		/// </param>
		public CullFaceState(FrontFace frontFace, Face culledFace)
		{
			mFrontFaceMode = frontFace;
			mEnabled = true;
			mCulledFace = culledFace;
		}

		/// <summary>
		/// Construct the current CullFaceState.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining this RenderState.
		/// </param>
		public CullFaceState(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.DebugEnabled) {
				if (ctx.IsCurrent() == false)
					throw new ArgumentException("not current", "ctx");
			}

			int frontFace, cullFaceMode;

			// Determine how front face is determined
			Gl.Get(Gl.FRONT_FACE, out frontFace);
			RenderException.DebugCheckErrors();
			mFrontFaceMode = (FrontFace) frontFace;

			// Determine whether face culling is enabled
			mEnabled = Gl.IsEnabled(Gl.CULL_FACE);
			RenderException.DebugCheckErrors();

			// Determine which faces are culled
			Gl.Get(Gl.CULL_FACE_MODE, out cullFaceMode);
			RenderException.DebugCheckErrors();
			mCulledFace = (Face) cullFaceMode;
		}
		
		#endregion
		
		#region Cull Face State Definition

		/// <summary>
		/// Polygon front face determination methods.
		/// </summary>
		public enum FrontFace
		{
			/// <summary>
			/// The polygon front-face is described by counter-clockwise sequence vertices (respect window coordinates).
			/// </summary>
			CounterClockWise = Gl.CCW,
			/// <summary>
			/// The polygon front-face is described by clockwise sequence vertices (respect window coordinates).
			/// </summary>
			ClockWise = Gl.CW
		}
		
		/// <summary>
		/// Polygon face types.
		/// </summary>
		/// <remarks>
		/// The Front faces are determined with method <see cref="CullFaceState.FrontFace"/>.
		/// </remarks>
		public enum Face
		{
			/// <summary>
			/// Front face.
			/// </summary>
			Front = Gl.FRONT,
			/// <summary>
			/// Back face.
			/// </summary>
			Back = Gl.BACK,
			/// <summary>
			/// Front and back faces.
			/// </summary>
			Both = Gl.FRONT_AND_BACK
		}

		/// <summary>
		/// Front face determination mode property.
		/// </summary>
		public FrontFace FrontFaceMode
		{
			get { return (mFrontFaceMode); }
			set { mFrontFaceMode = value; }
		}

		/// <summary>
		/// Cull enabled flag.
		/// </summary>
		public bool Culling
		{
			get { return (mEnabled); }
			set { mEnabled = value; }
		}

		/// <summary>
		/// Face culled.
		/// </summary>
		public Face CulledFace
		{
			get { return (mCulledFace); }
			set { mCulledFace = value; }
		}

		/// <summary>
		/// Front face determination
		/// </summary>
		private FrontFace mFrontFaceMode = FrontFace.CounterClockWise;

		/// <summary>
		/// Enabled flag.
		/// </summary>
		private bool mEnabled = true;

		/// <summary>
		/// Face culled.
		/// </summary>
		private Face mCulledFace = Face.Back;
		
		#endregion

		#region Default State

		/// <summary>
		/// The system default state for CullFaceState.
		/// </summary>
		public static CullFaceState DefaultState { get { return (new CullFaceState()); } }

		#endregion
		
		#region RenderState Overrides

		/// <summary>
		/// The name of this render context.
		/// </summary>
		public static readonly string StateId = "Gl.CullFace";

		/// <summary>
		/// The identifier of this RenderState.
		/// </summary>
		public override string Id { get { return (StateId); } }

		/// <summary>
		/// Flag indicating whether the state is context-bound.
		/// </summary>
		/// <remarks>
		/// It returns always true.
		/// </remarks>
		public override bool IsContextBound { get { return (true); } }
		
		/// <summary>
		/// Set ShaderProgram state.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> which has defined the shader program <paramref name="sProgram"/>.
		/// </param>
		/// <param name="sProgram">
		/// The <see cref="ShaderProgram"/> which has the state set.
		/// </param>
		public override void ApplyState(RenderContext ctx, ShaderProgram sProgram)
		{
			// Front face determination
			Gl.FrontFace((int)mFrontFaceMode);
			RenderException.DebugCheckErrors();
			// Culling
			if (mEnabled == true) {
				// Face culling
				Gl.Enable(Gl.CULL_FACE);
				RenderException.DebugCheckErrors();
				// Culled face
				Gl.CullFace((int)mCulledFace);
				RenderException.DebugCheckErrors();
			} else {
				//No face culling
				Gl.Disable(Gl.CULL_FACE);
				RenderException.DebugCheckErrors();
			}
		}

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">
		/// A <see cref="RenderState"/> to compare to this RenderState.
		/// </param>
		/// <returns>
		/// It returns true if the current object is equal to <paramref name="other"/>.
		/// </returns>
		/// <remarks>
		/// <para>
		/// This method test only whether <paramref name="other"/> type equals to this type.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="other"/> is null.
		/// </exception>
		public override bool Equals(IRenderState other)
		{
			if (base.Equals(other) == false)
				return (false);
			Debug.Assert(other is CullFaceState);

			CullFaceState otherState = (CullFaceState) other;

			if (otherState.mFrontFaceMode != mFrontFaceMode)
				return (false);
			if (otherState.mEnabled != mEnabled)
				return (false);
			if (otherState.mCulledFace != mCulledFace)
				return (false);

			return (true);
		}

		/// <summary>
		/// Performs a deep copy of this <see cref="IRenderState"/>.
		/// </summary>
		/// <returns>
		/// It returns the equivalent of this <see cref="IRenderState"/>, but all objects referenced
		/// are not referred by both instances.
		/// </returns>
		public override IRenderState Copy()
		{
			if (Culling)
				return (new CullFaceState(FrontFaceMode, CulledFace));
			else
				return (new CullFaceState(FrontFaceMode));
		}
		
		#endregion
	}
}
